#include "DotController.h"
#include <QMouseEvent>

#include "../Models/DotModel.h"
#include "../Views/DotView.h"
#include "../Settings/GameSettings.h"
#include <QMessageBox>
#include <QPainter>
#include <QInputDialog>
#include "../Views/StatusViewer.h"
#include "ComputerPlay.h"
#include <QThread>
#include "../Utils/Msg.h"
#include <QDateTime>
#include "../Utils/ResultLogger.h"
#include "../Network/Connection.h"
#include "../Network/NetworkManager.h"
#include "../Views/BuddyWnd.h"
#include "../Network/Messages/PlayRequestMsg.h"
#include "../Network/Messages/PlayRequestResultMsg.h"
#include "../Network/Messages/PeerViewInfoMsg.h"
#include "../Network/Messages/GameCanceledMsg.h"
#include "../Network/Messages/LineAddedMsg.h"
#include "../Views/ControlPanel.h"


DotController::DotController(DotView *pView, DotModel *pModel,QObject *parent) :
        QObject(parent),mpView(pView),mpModel(pModel),mGameTurn(Game::Unknown),mbGameStarted(false),mbGameFinshed(false),mbPalyingWithComputer(false)
{
    mpPeerPlayingWith = NULL;
    mpPeerSentRequest = NULL;
    mTimer.setSingleShot(false);
    mpDotSelected = NULL;
    mDotsSelectable.clear();
    mnRndRadius =0;
    mpNetManager = new NetworkManager(this);

    mpView->addListener(this);

    mpComputerPlay = new ComputerPlay(this);
    mpPosAnimation = new QPropertyAnimation(mpComputerPlay, "pos");
    mpPosAnimation->setLoopCount(1);
    mpPosAnimation->setDuration(SHORT_DURATION);

    mpAISelectorMax = new AILineSelectorAlgMax(this,this);
    mpAISelectorMin = new AILineSelectorAlgMin(this,this);

    connect(mpPosAnimation,SIGNAL(finished()),SLOT(posAnimationFinished()));
    connect(mpComputerPlay,SIGNAL(positionChanged()),mpView,SLOT(update()));
    connect(mpModel,SIGNAL(needToResized(int,int)),mpView,SLOT(needToResized(int,int)));
    connect(&mTimer,SIGNAL(timeout()),mpView,SLOT(update()));
    connect(this,SIGNAL(gameTurnChanged(Game::Player)),SLOT(turnChanged(Game::Player)));

    connect(mpNetManager,SIGNAL(participantLeft(Connection*)),SLOT(peerLeft(Connection*)));
    connect(mpNetManager,SIGNAL(playRequestCame(Connection*,PlayRequestMsg*)),SLOT(playRequestCame(Connection*,PlayRequestMsg*)));
    connect(mpNetManager,SIGNAL(playRequestResultCame(Connection*,PlayRequestResultMsg*)),SLOT(playRequestResultCame(Connection*,PlayRequestResultMsg*)));
    connect(mpNetManager,SIGNAL(gameCanceledMsgCame(Connection*,GameCanceledMsg*)),SLOT(gameCanceledMsgCame(Connection*,GameCanceledMsg*)));
    connect(mpNetManager,SIGNAL(lineAddedMsgCame(Connection*,LineAddedMsg*)),SLOT(lineAddedMsgCame(Connection*,LineAddedMsg*)));    
}

DotController::~DotController()
{
    delete mpNetManager;
}

NetworkManager *DotController::netManager() { return mpNetManager;}

void DotController::setCtrlPanel(ControlPanel *pPnl)
{
    mpCtrlPnl = pPnl;
}

bool DotController::startGame(Game::Player turnPlayer)
{
    if(turnPlayer == Game::Unknown && !GameSettings::me()->inputFromUserToSelectTurn()){
        StatusViewer::me()->showTempMsg(QString("You are willing not to play game."),LONG_DURATION);
        return false;
    }

    GameSettings *pGS = GameSettings::me();
    mpDotSelected = NULL;
    mDotsSelectable.clear();
    mpModel->setDotsHorz_Vert(pGS->setting(Game::DotsPerRow).value<uint>(),pGS->setting(Game::DotsPerColumn).value<uint>());
    mpModel->setLineLengthHorz_Vert(pGS->setting(Game::CellWidth).value<uint>(),pGS->setting(Game::CellHeight).value<uint>());
    uint nMargin = pGS->setting(Game::FieldMargin).value<uint>();
    mpModel->setStartPoint(QPoint(nMargin,nMargin),true);
    mGameTurn = turnPlayer == Game::Unknown ? pGS->playerTurn() : turnPlayer;
    mbGameStarted = true;
    mbGameFinshed = false;
    mbPalyingWithComputer = GameSettings::me()->playingWithComputer();
    emit gameStarted();
    emit gameTurnChanged(mGameTurn);

    return true;
}

bool DotController::cancelGame(bool bShowMsg,bool bSendMsg)
{
    if(isGameRunning()){
        if(bShowMsg && Msg::ask("A game already running.","Do you want to stop it?") == QMessageBox::No){
        }
        else{
            if(bSendMsg && mpPeerPlayingWith){
                GameCanceledMsg *pMsg = new GameCanceledMsg(tr("Extremely sorry. I have another plan."));
                QString msg = QInputDialog::getText(GameSettings::me()->mostTopWnd(),
                                                    tr("Message to ") + mpPeerPlayingWith->peerViewInfo()->name()
                                                    ,tr("Cancel Messsage"),QLineEdit::Normal,tr("Extremely sorry. I have another plan."));
                if(!(msg.isNull() || msg.isEmpty())){
                    pMsg->setString(msg);
                }

                mpPeerPlayingWith->sendMessage(pMsg);
                setPlayingPeer(NULL);
            }

            mLastLineAddeByPeer = QLine();
            mpModel->reset();
            mbGameFinshed = false;
            mbGameStarted = false;
            emit gameCanceled();
            mpView->update();

            return true;
        }
    }

    return false;
}

DotModel *DotController::model()const{return mpModel;}

DotView *DotController::view()const{return mpView;}

Dot *DotController::findDotNearToPoint(QPoint pos)
{
    QList<Dot*> dots = mpModel->dotsNearToPoint(pos);

    return dots.count() > 0 ? dots[0] : NULL ;
}

void DotController::addALine(Dot *pDot1, Dot *pDot2)
{
    Line *pLine = new Line(pDot1,pDot2,mGameTurn);
    int nResult = mpModel->addLine(pLine);
    mpView->update();
    emit lineAdded(mGameTurn);

    if(mpPeerPlayingWith && mGameTurn == Game::Player1){
        LineAddedMsg *pMsg = new LineAddedMsg(pDot1->row(),pDot1->column(),pDot2->row(),pDot2->column());
        mpPeerPlayingWith->sendMessage(pMsg);
    }

    if(nResult == 2){
        // game over; you can get result
        mLastLineAddeByPeer = QLine();
        mbGameFinshed = true;
        mbGameStarted = false;
        emit gameFinished(winner());
        logThisGame();

        if(mpPeerPlayingWith && winner() == Game::Player2){
            Msg::info("Shame on you!!! You lose the game.",tr("Your Score: (") +  QString::number(mpModel->playerCellsCount(loser())) +
                      QString(" / ") + QString::number(mpModel->totalCells()) + tr(")"));
        }
        else{
            Msg::info(winner() == Game::Unknown ? "Game Result" : QString("Congrats ") +
                      GameSettings::me()->playerName(winner()) + " !!!",winner() == Game::Unknown ? "Game Drawn!!!" :
                      (QString(GameSettings::me()->playerName(winner()) + " wins the game by ") +
                       QString::number(mpModel->playerCellsCount(winner())) + QString(" / ") +
                       QString::number(mpModel->totalCells())));
        }

        setPlayingPeer(NULL);
    }
    else if(nResult == 1){
        emit cellOwned(mGameTurn);

        if(mbPalyingWithComputer && mGameTurn == Game::Player2)
        {
            resumeComputerPlaying();
        }
    }
    else if(nResult == 0){
        mGameTurn = mGameTurn == Game::Player1 ? Game::Player2 : Game::Player1; // changing turn to other player

        emit gameTurnChanged(mGameTurn);
    }
    else if(nResult == -1){
        StatusViewer::me()->showTempMsg("WRONG: You wanted to add already added line",LONG_DURATION + LONG_DURATION);
        delete pLine;
    }
}

void DotController::aDotSelected(Dot *pDot)
{
    if(!mpDotSelected){
        mnRndRadius = 0;
        mpDotSelected = pDot;
        mDotsSelectable = mpModel->dotsEligibleFrom(pDot);
        mTimer.start(20);
        return;
    }

    if(isEligibleForLine(mpDotSelected,pDot)){
        mTimer.stop();
        mDotsSelectable.clear();
        Dot *pDot1 = mpDotSelected;
        mpDotSelected = NULL;
        addALine(pDot1,pDot);
    }
}

void DotController::aDotClicked(QPoint pos)
{
    Dot *pDot = findDotNearToPoint(pos);
    QString msg = "Now click on blooming cells";

    if(pDot == NULL && mpDotSelected == NULL){
        msg = "Click on the dot properly";
    }

    StatusViewer::me()->showTempMsg(msg,LONG_DURATION);

    if(pDot && pDot->isSelectable()){
        aDotSelected(pDot);
    }
}

void DotController::mousePressEvent(QMouseEvent *pEvent)
{
    if((mbPalyingWithComputer && mGameTurn == Game::Player2) ||
       (mpPeerPlayingWith && mGameTurn == Game::Player2)){
        return;
    }

    if(pEvent->button() == Qt::LeftButton){
        aDotClicked(pEvent->pos());
    }
    else{
        mpDotSelected = NULL;
        mDotsSelectable.clear();
        mTimer.stop();
        mpView->update();
    }
}

bool DotController::isEligibleForLine(Dot *pDot1, Dot *pDot2)
{
    if(pDot1 && pDot2){
        return ((((unsigned int)(qAbs(pDot1->x() - pDot2->x()))) == mpModel->lineLengthHorz()) &&
                (pDot1->y() == pDot2->y())) ||
                ((((unsigned  int)(qAbs(pDot1->y() - pDot2->y()))) == mpModel->lineLengthVert()) &&
                 (pDot1->x() == pDot2->x()));
    }

    return false;
}

bool DotController::isGameStarted()
{
    return mbGameStarted;
}

bool DotController::isGameRunning()
{
    return mbGameStarted && !mbGameFinshed;
}

bool DotController::isGameFinished() { return mbGameFinshed;}

void DotController::draw(QPainter *pDC)
{
    if(mpDotSelected){
        uint nExtra = 5;
        mnRndRadius++;

        if(mnRndRadius > (GameSettings::me()->setting(Game::DotRadii).value<uint>() + nExtra)){
            mnRndRadius = 0;
        }

        mpDotSelected->drawEllipse(pDC,mnRndRadius,GameSettings::me()->setting(Game::DotSelectedColor).value<QColor>());

        for(int i = 0; i < mDotsSelectable.count(); i++){
            mDotsSelectable.at(i)->drawEllipse(pDC,mnRndRadius,GameSettings::me()->setting(Game::DotSelectableColor).value<QColor>());
        }

        pDC->setPen(GameSettings::me()->setting(Game::PointMovable).value<QColor>());
        QPoint ptMoving = mbPalyingWithComputer && mGameTurn == Game::Player2 ? mpComputerPlay->pos() :  mpView->mapFromGlobal(QCursor::pos());
        pDC->drawLine(mpDotSelected->point(),ptMoving);
        QLineF l = QLineF(ptMoving,mpDotSelected->point());
        QLineF l1 = l,l2 = l;
        qreal angle = l.angle();
        l1.setAngle(angle + 30);
        l1.setLength(10);
        pDC->drawLine(l1);
        l2.setAngle(angle - 30);
        l2.setLength(10);
        pDC->drawLine(l2);
    }

    if(mpPeerPlayingWith && isGameRunning() && !mLastLineAddeByPeer.isNull()){
        static int  nnWidth = 1;
        QPen pen = GameSettings::me()->pen(Game::Player2);
        pen.setWidth(nnWidth);
        pen.setCapStyle(Qt::RoundCap);
        pDC->setPen(pen);
        pDC->drawLine(mLastLineAddeByPeer);
        nnWidth++;

        if(GameSettings::me()->setting(Game::DotRadii).value<int>() < nnWidth){
            nnWidth = 1;
        }
    }
}

Game::Player DotController::winner()
{
    uint nCellPlayer1 = mpModel->playerCellsCount(Game::Player1);
    uint nCellPlayer2 = mpModel->playerCellsCount(Game::Player2);

    if(nCellPlayer1 > nCellPlayer2){
        return Game::Player1;
    }
    else if(nCellPlayer1 < nCellPlayer2){
        return Game::Player2;
    }
    else{
        return Game::Unknown;
    }
}

Game::Player DotController::loser()
{
    return winner() == Game::Player1 ? Game::Player2 : (winner() == Game::Player2 ? Game::Player1 : Game::Unknown);
}

Game::Player DotController::gameTurner(){return mGameTurn;}

void DotController::posAnimationFinished()
{
    aDotClicked(mpComputerPlay->pos());
}

void DotController::turnChanged(Game::Player player)
{
    if(mbPalyingWithComputer && player == Game::Player2){
        resumeComputerPlaying();
    }
}

void DotController::resumeComputerPlaying()
{    
    QThread::currentThread()->wait(3000);
    QLine line = mpAISelectorMax->selectLine();

    aDotClicked(line.p1());
    mpPosAnimation->setStartValue(line.p1());
    mpPosAnimation->setEndValue(line.p2());
    mpPosAnimation->start();
}

void DotController::logThisGame()
{
    QStringList strInfo;
    strInfo << GameSettings::me()->playerName(winner());
    strInfo << GameSettings::me()->playerName(loser());
    strInfo << (mbPalyingWithComputer ? GameSettings::me()->modeName() : QString("NA"));
    strInfo << (QString::number(mpModel->playerCellsCount(winner())) + QString(" / ") + QString::number(mpModel->totalCells()));
    QString strData = QDateTime::currentDateTime().toString(Qt::ISODate);
    strData = strData.replace('T',' ');
    strInfo << strData;

    ResultLogger::me()->addAResult(strInfo);
}

void DotController::askForPlaying(Connection *pToWhom)
{
    if(mpPeerSentRequest){
        if(Msg::ask(tr("Do you want to cancel the previous request?"),
                    tr("A request is already sent to ") + mpPeerSentRequest->peerViewInfo()->name()) == QMessageBox::No){
            return;
        }
    }

    if(isGameRunning() && !cancelGame(true,true)){
        return;
    }

    PlayRequestMsg *pMsg = new PlayRequestMsg(GameSettings::me()->setting(Game::DotsPerRow).value<int>(),
                                              GameSettings::me()->setting(Game::DotsPerColumn).value<int>());

    QString msg = QInputDialog::getText(GameSettings::me()->mostTopWnd(),
                                    tr("Message to ") + pToWhom->peerViewInfo()->name()
                                    ,tr("Request Messsage"),QLineEdit::Normal,tr("Please play with me"));
    if(msg.isNull() || msg.isEmpty()){
        msg = tr("Please play with me");
    }

    mpPeerSentRequest = pToWhom;
    pMsg->setString(msg);
    pToWhom->sendMessage(pMsg);    
}

void DotController::playRequestCame(Connection *pFrom, PlayRequestMsg *pMsg)
{
    QString msgCaution;
    if(isGameRunning()){
        msgCaution = mpPeerPlayingWith ? tr("You are playing a game with ") + mpPeerPlayingWith->peerViewInfo()->name() :
                  tr("You are playing a game currently.");
        msgCaution += tr("If you accept this request, running game will be canceled.");
    }
    QString msg = tr("A new game play request came from ") + pFrom->peerViewInfo()->name() +
                     tr(". His message is '") + pMsg->string() + tr("'. \n") +
                     tr("Do you want to accept his request?");
    PlayRequestResultMsg *pResult = new PlayRequestResultMsg();

    if(Msg::ask(msg,msgCaution) == QMessageBox::Yes){
        emit networkGameStartingParameterRequest(pMsg->dotsInRow(),pMsg->dotsInColumn(),
                                                 pFrom->peerViewInfo()->name(),pFrom->peerViewInfo()->color());
        if(isGameRunning()){
            cancelGame(false,true);
        }

        pResult->setString(tr("Thank you for your willing to play with me."));
        pResult->setDotsInRow(pMsg->dotsInRow());
        pResult->setDotsInColumn(pMsg->dotsInColumn());
        pResult->setStatus(PlayRequestResultMsg::Accepted);
        setPlayingPeer(pFrom);
        startGame(Game::Player2);
    }
    else{
        QString msg = QInputDialog::getText(GameSettings::me()->mostTopWnd(),
                                        tr("Message to ") + pFrom->peerViewInfo()->name()
                                        ,tr("Reason to reject"),QLineEdit::Normal,tr("Sorry, I am busy now."));
        if(msg.isNull() || msg.isEmpty()){
            msg = tr("Sorry, I am busy now.");
        }

        pResult->setString(msg);
        pResult->setStatus(PlayRequestResultMsg::Rejected);
    }

    pFrom->sendMessage(pResult);
}

void DotController::playRequestResultCame(Connection *pFrom, PlayRequestResultMsg *pMsg)
{
    if(pFrom != mpPeerSentRequest){
        if(pMsg->status() == PlayRequestResultMsg::Accepted){
            GameCanceledMsg *pMsg = new GameCanceledMsg(tr("Extremely sorry. There was a mistake."));
            pFrom->sendMessage(pMsg);
        }
    }
    else{
        if(mpPeerSentRequest){
            mpPeerSentRequest = NULL;

            if(pMsg->status() == PlayRequestResultMsg::Accepted){
                emit networkGameStartingParameterRequest(pMsg->dotsInRow(),pMsg->dotsInColumn(),
                                                         pFrom->peerViewInfo()->name(),pFrom->peerViewInfo()->color());
                setPlayingPeer(pFrom);
                Msg::info(tr("Game playing request accepted by ") + pFrom->peerViewInfo()->name() +
                          tr(". His message is bellow..."),pMsg->string());
                startGame(Game::Player1);
            }
            else{
                Msg::info(tr("Game playing request denied by ") + pFrom->peerViewInfo()->name(),pMsg->string());
            }
        }
    }
}

void DotController::peerLeft(Connection *pWho)
{
    if(mpPeerPlayingWith && pWho == mpPeerPlayingWith){
        Msg::warn("This playing session is going to quite.",
                  mpPeerPlayingWith->peerViewInfo()->name() + " left the the network.");
        cancelGame(false,false);
        setPlayingPeer(NULL);
    }
}

void DotController::gameCanceledMsgCame(Connection *pFrom, GameCanceledMsg *pMsg)
{
    QString msg = mpPeerPlayingWith == pFrom ? "This playing session is going to quite." : "A previous game session is canceled.";
    Msg::warn(msg, "Game canceled by " + pFrom->peerViewInfo()->name() +
              (pMsg->string().trimmed().isNull() || pMsg->string().trimmed().isEmpty()));

    if(mpPeerPlayingWith == pFrom){
        cancelGame(false,false);
        setPlayingPeer(NULL);
    }
}

void DotController::lineAddedMsgCame(Connection *pFrom, LineAddedMsg *pMsg)
{
    if(mpPeerPlayingWith == pFrom){
        Dot *pDot1 = mpModel->dots()->get(pMsg->dot1Row(),pMsg->dot1Column());
        Dot *pDot2 = mpModel->dots()->get(pMsg->dot2Row(),pMsg->dot2Column());
        mLastLineAddeByPeer = QLine(pDot1->point(),pDot2->point());
        mTimer.start(100);
        addALine(pDot1,pDot2);
    }
}

void DotController::setPlayingPeer(Connection *pPeer)
{
    if(mpPeerPlayingWith){
        QObject::disconnect(mpPeerPlayingWith,SIGNAL(peerViewInfoChanged()),this,SLOT(playingPeerViewInfoChanged()));
    }

    mpPeerPlayingWith = pPeer;

    if(mpPeerPlayingWith){
        QObject::connect(mpPeerPlayingWith,SIGNAL(peerViewInfoChanged()),this,SLOT(playingPeerViewInfoChanged()));
    }

    mpNetManager->setPlayingWith(pPeer);
}

void DotController::playingPeerViewInfoChanged()
{
    Connection *pSender = qobject_cast<Connection*>(sender());

    if(pSender == mpPeerPlayingWith && isGameRunning()){
        emit networkGamePlayingViewInfoChanged(pSender->peerViewInfo()->name(),pSender->peerViewInfo()->color());
    }
}
